//
// The resource management layer has been upgraded, thus deprecating this file.
//
#error *** resfile.cpp has been deprecated. ***

#include <stdlib.h>
#include <string.h>

#include "lib.h"
#include "stream.h"
#include "hashtabl.h"
#include "resfile.h"
#include "resentry.h"
#include "zlib.h"


// During resource file creation, resource entries are either uncompressed or
// compressed.  This define controls that behavior.
//#define COMPRESS_RESOURCES

pCResFile
newCResFile(const String & ResFileName)
{
	pCResFile	resfile = (pCResFile)malloc(sizeof(CResFile));

	if (resfile)
	{
		if (CResFileConstruct(resfile, ResFileName) == false)
		{
			deleteCResFile(resfile);
			resfile = NULL;
		}
	}

	return resfile;
}


void
deleteCResFile(pCResFile This)
{
	if (This)
	{
		CResFileDestruct(This);
		free(This);
	}
}


bool8
CResFileConstruct(pCResFile This, const String & ResFileName)
{
	RFHeader	fileheader;
	RFIndex *	groups;
	RFEntry *			entries;
	RFCompressedEntry *	centries;
	pCResEntry	resentry;
	uint32		gloop, eloop;
	uint32		groupsize, entrysize;
	int32		status;

	This->cStream = newCStream(ResFileName, streamO_RDONLY);
	This->cHashTable = newCHashTable(1023, CResFileHashDelete, 
		(void *)This, NULL);

	if (This->cStream)
	{
		status = CStreamRead(This->cStream, &fileheader, sizeof(RFHeader));
		if ((status == streamEOF) ||
			(strcmp(RFMagic, (const char *)fileheader.cMagic))
#ifndef DEBUG
			|| fileheader.cVersion[0] < VERSION_MAJOR	//Art 3.0.0 == 16bit
#endif
			)
		{
			DEBUGMSG(("CResFileConstruct(): Bad Magic for file '%s'.\n", \
				ResFileName));
			return false;
		}

		memcpy(This->cVersion, &fileheader.cVersion, 4);
		memcpy(This->cAttributes, &fileheader.cAttributes, 4);

// We need to check here to insure that this resource is in the
//   proper format.  i.e. we need to get its endianess.  If it
//   is not the correct endian we should abort now!.
//   BUT, because we don't have endian'ng working...we won't do this now.

		groupsize	= sizeof(RFIndex) * fileheader.cIndexEntries;
		groups		= (RFIndex *)malloc(groupsize);

		CStreamSeek(This->cStream, fileheader.cIndexOffset, streamSEEK_SET);
		CStreamRead(This->cStream, groups, groupsize);

		for (gloop = 0; gloop < fileheader.cIndexEntries; gloop++)
		{
			if (This->cAttributes[1] == 'C')
			{
				//compressed
				entrysize = sizeof(RFCompressedEntry) * groups[gloop].cEntries;
				centries  = (RFCompressedEntry *)malloc(entrysize);

				CStreamSeek(This->cStream, groups[gloop].cOffset, 
					streamSEEK_SET);
				CStreamRead(This->cStream, centries, entrysize);

				for (eloop = 0; eloop < groups[gloop].cEntries; eloop++)
				{
					// This is a potentially slow and memory fragmenting process.
					//   I hope to fix this soon.  -kral
					resentry = newCCompressedResEntry(&centries[eloop], 
						groups[gloop].cGroupID);

					CHashTableAddDataByName(This->cHashTable, 
						(char *)(centries[eloop].cName), resentry);
				}

				free(centries);
			}
			else
			{
				entrysize	= sizeof(RFEntry) * groups[gloop].cEntries;
				entries		= (RFEntry *)malloc(entrysize);

				CStreamSeek(This->cStream, groups[gloop].cOffset, 
					streamSEEK_SET);
				CStreamRead(This->cStream, entries, entrysize);

				for (eloop = 0; eloop < groups[gloop].cEntries; eloop++)
				{
					// This is a potentially slow and memory fragmenting process.
					//   I hope to fix this soon.  -kral
					resentry = newCResEntry(&entries[eloop], 
						groups[gloop].cGroupID);

					CHashTableAddDataByName(This->cHashTable, 
						(char *)(entries[eloop].cName), resentry);
				}
				
				free(entries);
			}
		}
		free(groups);

		return true;
	}

	return false;
}


void
CResFileDestruct(pCResFile This)
{
	if (This)
	{
		deleteCStream(This->cStream);
		deleteCHashTable(This->cHashTable);
	}
}


void
CResFileHashDelete(pCHashTable HashTable, void * HashData, void * UserData)
{
	NOTUSED2(HashTable, UserData);

	deleteCResEntry((pCResEntry)HashData);
}


void *
CResFileGetResource(pCResFile This, char * ResName)
{
	pCResEntry	resentry;
	void *		resdata;
	void *		data;
	uint32		length;
	
	resentry = (pCResEntry)CHashTableGetDataByName(This->cHashTable, ResName);
	resdata  = 0;

	if (resentry)
	{
		resdata = CResEntryGetData(resentry);
		if (resdata == NULL)
		{
			resdata = CResEntryAllocDataSpace(resentry);
			if (resdata)
			{
				if (resentry->cCompressedSize)
				{
					data = malloc(resentry->cCompressedSize);
					if (!data)
					{
						CResEntryReleaseData(resdata);
						return 0;
					}

					CStreamSeek(This->cStream, CResEntryOffset(resentry), 
						streamSEEK_SET);
					CStreamRead(This->cStream, data, resentry->cCompressedSize);

					length = CResEntrySize(resentry);
					if (Z_OK != uncompress((uint8*)resdata, &length,
						(uint8*)data, resentry->cCompressedSize))
					{
						free(data);
						data = 0;

						CResEntryReleaseData(resdata);
						return 0;
					}

					free(data);
					data = 0;
				}
				else
				{
					CStreamSeek(This->cStream, CResEntryOffset(resentry), 
						streamSEEK_SET);
					CStreamRead(This->cStream, resdata,
						CResEntrySize(resentry));
				}
			}
		}
	}
	
	return resdata;
}


bool8
CResFileReleaseResource(pCResFile This, void * ResData)
{
	NOTUSED(This);

	// Just following the chain of command.
	return CResEntryReleaseData(ResData);

	// NOTE: We should really check here to see if this was a 'registered'
	//       Resource.  If it is (and if the usecount drops to 0) we should
	//       remove the entry from the resfile hashtable so we don't attempt
	//       to read in from a non-existant file (or offset = 0).
}


//----------------------------------------------------------------------------
//  Everything below this line has been written specifically for the 
//     Resource Compiler.
//
#ifdef RESCOMPILER

#include "endian.h"

pCResFile
createCResFile(uint8 Endian)
{
	pCResFile	resfile = (pCResFile)calloc(sizeof(CResFile), 1);

	if (resfile)
	{
		resfile->cHashTable = newCHashTable(1023, CResFileHashDelete, 
			(void *)resfile, NULL);

		resfile->cVersion[0] = VERSION_MAJOR;
		resfile->cVersion[1] = VERSION_MINOR;
		resfile->cVersion[2] = VERSION_PATCH;
		resfile->cVersion[3] = VERSION_BUILD;

		resfile->cAttributes[0] = Endian;
#ifdef COMPRESS_RESOURCES
		resfile->cAttributes[1] = 'C';				// Compressed resources.
#endif
		resfile->cAttributes[2] = 'L';				// Long filenames.
		resfile->cAttributes[3] = 'S';				// Standard header.
	}

	return resfile;
}

static int
CResFileQSortCompare(RFEntry * A, RFEntry * B)
{
	return (strncmp(A->cName, B->cName, resfileNAME_LENGTH));
}

void
CResFileDumpToFile(pCResFile This, const String & Filename)
{
	pCStream			sp;
	pCResEntry			resentry;
	uint8 *				resdata;
	pCHashTable			hashtable;
	uint32				loop;
	uint32				desired_endian;
	RFHeader *			rfheader;
	RFIndex *			rfindex;
#ifndef COMPRESS_RESOURCES
	RFEntry *			rfentry;
#else
	RFCompressedEntry *	rfentry;
	uint8 *				compressedresdata;
	int32				result;
#endif

	sp = newCStream(Filename, streamO_WRONLY);
	if (sp)
	{
		rfheader = (RFHeader *)calloc(sizeof(RFHeader), 1);
		memcpy(rfheader->cMagic, RFMagic, strlen(RFMagic));

		memcpy(rfheader->cVersion, This->cVersion, 4);
		memcpy(rfheader->cAttributes, This->cAttributes, 4);

		desired_endian = This->cAttributes[0];

		hashtable = CResFileBuildGroupIndex(This, &rfheader->cIndexEntries);

#if 0
		if (rfheader->cIndexEntries == 0)
		{
			// No groups...no entries...ABORT!
		}
#endif

		// Temporary...to reserve space for true header later.
		CStreamWrite(sp, rfheader, sizeof(RFHeader));

		CHashTableIterate(hashtable, true);

		while ((rfindex = 
			(RFIndex *)CHashTableIterate(hashtable, false)) != NULL)
		{
			loop	= 0;
#ifdef COMPRESS_RESOURCES
			rfentry	= (RFCompressedEntry *)
				calloc(sizeof(RFCompressedEntry) * rfindex->cEntries, 1);
#else
			rfentry	= (RFEntry *)calloc(sizeof(RFEntry) * rfindex->cEntries, 1);
#endif

			CHashTableIterate(This->cHashTable, true);

			while ((resentry = 
				(pCResEntry)CHashTableIterate(This->cHashTable, false)) != NULL)
			{
				if (rfindex->cGroupID == CResEntryGroup(resentry))
				{
					strcpy(rfentry[loop].cName, CResEntryName(resentry));
					rfentry[loop].cSize = CResEntrySize(resentry);

					loop++;
				}
			}

			// Sort here!
#ifdef COMPRESS_RESOURCES
			qsort(rfentry, rfindex->cEntries, sizeof(RFCompressedEntry),
				(int (*)(const void *, const void *))CResFileQSortCompare);
#else
			qsort(rfentry, rfindex->cEntries, sizeof(RFEntry),
				(int (*)(const void *, const void *))CResFileQSortCompare);
#endif

			// Write individual RFEntry DATA out to file.
			for (loop = 0; loop < rfindex->cEntries; loop++)
			{
				resentry = (pCResEntry)CHashTableGetDataByName(This->cHashTable,
					rfentry[loop].cName);

#ifdef COMPRESS_RESOURCES
				rfentry[loop].cOffset = CStreamTell(sp);
				rfentry[loop].cCompressedSize = CResEntrySize(resentry);

				compressedresdata = (uint8 *)malloc(CResEntrySize(resentry));
				resdata = (uint8 *)CResFileGetResource(This, rfentry[loop].cName);

				result = compress(compressedresdata,
							&rfentry[loop].cCompressedSize, resdata,
							rfentry[loop].cCompressedSize);

				if (result == Z_OK)
				{
					CStreamWrite(sp,
						compressedresdata, rfentry[loop].cCompressedSize);

					free(compressedresdata);
					compressedresdata = 0;
				}
				else if (result == Z_BUF_ERROR)
				{
					free(compressedresdata);
					compressedresdata = 0;

					rfentry[loop].cCompressedSize = 0;

					CStreamWrite(sp, resdata, CResEntrySize(resentry));
				}
#else
				rfentry[loop].cOffset = CStreamTell(sp);
				resdata = (uint8 *)CResFileGetResource(This, rfentry[loop].cName);
				CStreamWrite(sp, resdata, CResEntrySize(resentry));
#endif
				CResFileReleaseResource(This, resdata);

				// We should be done with the rfentry (index) so endian it!
				if (desired_endian == endianBIG_ENDIAN)
					endianM2B(&(rfentry[loop]), endianRFEntry);
				else
					endianM2L(&(rfentry[loop]), endianRFEntry);
			}

			// Write RFEntry Table out to file.
			rfindex->cOffset = CStreamTell(sp);
#ifdef COMPRESS_RESOURCES
			CStreamWrite(sp, rfentry,
				sizeof(RFCompressedEntry) * rfindex->cEntries);
#else
			CStreamWrite(sp, rfentry, sizeof(RFEntry) * rfindex->cEntries);
#endif

			free(rfentry);
		}


		rfheader->cIndexOffset = CStreamTell(sp);

		// Write out each group.
		CHashTableIterate(hashtable, true);

		while ((rfindex = 
			(RFIndex *)CHashTableIterate(hashtable, false)) != NULL)
		{
			if (desired_endian == endianBIG_ENDIAN)
				endianM2B(rfindex, endianRFIndex);
			else
				endianM2L(rfindex, endianRFIndex);

			CStreamWrite(sp, rfindex, sizeof(RFIndex));
			free(rfindex);		// So we don't need a hashtable delete func.
		}

		// Destroy rfindex.
		deleteCHashTable(hashtable);

		// Update header.
		if (desired_endian == endianBIG_ENDIAN)
			endianM2B(rfheader, endianRFHeader);
		else
			endianM2L(rfheader, endianRFHeader);

		CStreamSeek(sp, 0, streamSEEK_SET);
		CStreamWrite(sp, rfheader, sizeof(RFHeader));

		free(rfheader);

		// Close the file...
		deleteCStream(sp);
	}
}


pCHashTable
CResFileBuildGroupIndex(pCResFile This, uint32 * GroupCount)
{
	pCHashTable		hashtable	= newCHashTable(23, NULL, NULL, NULL);
	pCResEntry		resentry;
	RFIndex *		rfindex;


	CHashTableIterate(This->cHashTable, true);

	while ((resentry = 
		(pCResEntry)CHashTableIterate(This->cHashTable, false)) != NULL)
	{
		rfindex = (RFIndex *)CHashTableGetDataByID(hashtable, 
			CResEntryGroup(resentry));

		if (rfindex == NULL)
		{
			rfindex = (RFIndex *)malloc(sizeof(RFIndex));

			rfindex->cGroupID = CResEntryGroup(resentry);
			rfindex->cOffset  = 0;
			rfindex->cEntries = 0;

			CHashTableAddDataByID(hashtable, rfindex->cGroupID, rfindex);

			(*GroupCount)++;
		}

		rfindex->cEntries++;
	}

	return hashtable;
}


bool8
CResFileAddResourceByResName(pCResFile This, char * ResName, uint32 ResGrp, 
	void * ResData, uint32 Length)
{
	pCResEntry	resentry;

	NOTUSED(This);

	resentry = newCResEntryWithData(ResName, ResGrp, ResData, Length);

	if (CHashTableAddDataByName(This->cHashTable, ResName, resentry) == false)
	{
		deleteCResEntry(resentry);
		return false;
	}

	return true;
}


bool8
CResFileAddResourceByResGrp(pCResFile This, char * ResName, uint32 ResGrp, 
	void * ResData, uint32 Length)
{
	pCResEntry	resentry;

	NOTUSED(This);

	resentry = newCResEntryWithData(ResName, ResGrp, ResData, Length);

	if (CHashTableAddDataByID(This->cHashTable, ResGrp, resentry) == false)
	{
		deleteCResEntry(resentry);
		return false;
	}

	return true;
}

bool8
CResFileRemoveResourceByResName(pCResFile This, char *ResName, uint32 ResGrp)
{
	pCResEntry	resentry;
	bool8 found = false;

	resentry = (pCResEntry)CHashTableGetDataByName(This->cHashTable, ResName);
	
	if (resentry && 0 == stricmp(CResEntryName(resentry), ResName)
#ifdef USEGROUP
		&& CResEntryGroup(resentry) == ResGrp)
#else
		)
#endif
	{
		deleteCResEntry(resentry);
		CHashTableRemoveDataByName(This->cHashTable, ResName);

		found = true;
	}

	return found;
}

void
CResFileDumpTableOfContents(pCResFile This)
{
	pCResEntry		resentry;


	CHashTableIterate(This->cHashTable, true);

	printf("\n      Name           Group    Size      Offset    Hash'd\n");
	printf("-------------------------------------------------------\n");
	while ((resentry = 
		(pCResEntry)CHashTableIterate(This->cHashTable, false)) != NULL)
	{
		printf("%16s   %8ld   %8ld   %8ld   %08x\n",
			CResEntryName(resentry),
			CResEntryGroup(resentry),
			CResEntrySize(resentry),
			CResEntryOffset(resentry),
			0);
	}
}

#endif
